﻿using System;
using DiceRoller.Dice;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace DiceRollingTests
{
    [TestClass()]
    public class DiceTests
    {
        [TestMethod()]
        public void ConstructorShouldRequireDiceParameter()
        {
            try
            {
                new Dice(null);
                Assert.Fail("Exception should have been thrown");
            }
            catch (ArgumentNullException exception)
            {
                Assert.AreEqual("Precondition failed: dice != null  dice\r\nParameter name: dice", exception.Message);
                Assert.AreEqual("dice", exception.ParamName);
            }
        }

        [TestMethod()]
        public void ConstructorShouldRequireAtLeastOneDie()
        {
            try
            {
                new Dice(new Die[0]);
                Assert.Fail("Exception should have been thrown");
            }
            catch (ArgumentException exception)
            {
                Assert.AreEqual("Precondition failed: dice.Count() > 0  dice\r\nParameter name: dice", exception.Message);
                Assert.AreEqual("dice", exception.ParamName);
            }
        }

        [TestMethod()]
        public void ConstructorShouldRequireAllDieToNotBeNull()
        {
            try
            {
                new Dice(new Die[] { null });
                Assert.Fail("Exception should have been thrown");
            }
            catch (ArgumentException exception)
            {
                Assert.AreEqual("Precondition failed: Contract.ForAll(dice, die => die != null)  dice\r\nParameter name: dice", exception.Message);
                Assert.AreEqual("dice", exception.ParamName);
            }
        }

        [TestMethod()]
        public void MinimumValueShouldSumAllDice()
        {
            var mock1 = new Mock<Die>(MockBehavior.Strict, 1, 2);
            mock1.SetupGet(die => die.MinimumValue).Returns(1);

            var mock2 = new Mock<Die>(MockBehavior.Strict, 2, 6);
            mock2.SetupGet(die => die.MinimumValue).Returns(2);

            var dice = new Dice(mock1.Object, mock2.Object);
            var result = dice.MinimumValue;

            Assert.AreEqual(3, result);
        }

        [TestMethod()]
        public void MinimumValueShouldAllowOverflowException()
        {
            var mock1 = new Mock<Die>(MockBehavior.Strict, 1, Int32.MaxValue);
            mock1.SetupGet(die => die.MinimumValue).Returns(Int32.MaxValue);

            var mock2 = new Mock<Die>(MockBehavior.Strict, 2, Int32.MaxValue);
            mock2.SetupGet(die => die.MinimumValue).Returns(Int32.MaxValue);

            var dice = new Dice(mock1.Object, mock2.Object);

            try
            {
                (dice.MinimumValue).ToString(); // get value
                Assert.Fail("Exception should have been thrown");
            }
            catch (OverflowException exception)
            {
                Assert.AreEqual("Arithmetic operation resulted in an overflow.", exception.Message);
            }
        }

        [TestMethod()]
        public void MaximumValueShouldSumAllDice()
        {
            var mock1 = new Mock<Die>(MockBehavior.Strict, 1, 2);
            mock1.SetupGet(die => die.MinimumValue).Returns(1);
            mock1.SetupGet(die => die.MaximumValue).Returns(2);

            var mock2 = new Mock<Die>(MockBehavior.Strict, 1, 6);
            mock2.SetupGet(die => die.MinimumValue).Returns(1);
            mock2.SetupGet(die => die.MaximumValue).Returns(6);

            var dice = new Dice(mock1.Object, mock2.Object);
            var result = dice.MaximumValue;

            Assert.AreEqual(8, result);
        }

        [TestMethod()]
        public void MaximumValueShouldAllowOverflowException()
        {
            var mock1 = new Mock<Die>(MockBehavior.Strict, 1, Int32.MaxValue);
            mock1.SetupGet(die => die.MinimumValue).Returns(1);
            mock1.SetupGet(die => die.MaximumValue).Returns(Int32.MaxValue);

            var mock2 = new Mock<Die>(MockBehavior.Strict, 1, Int32.MaxValue);
            mock2.SetupGet(die => die.MinimumValue).Returns(1);
            mock2.SetupGet(die => die.MaximumValue).Returns(Int32.MaxValue);

            var dice = new Dice(mock1.Object, mock2.Object);

            try
            {
                (dice.MaximumValue).ToString(); // get value
                Assert.Fail("Exception should have been thrown");
            }
            catch (OverflowException exception)
            {
                Assert.AreEqual("Arithmetic operation resulted in an overflow.", exception.Message);
            }
        }

        [TestMethod()]
        public void RollShouldSumAllDice()
        {
            var mock1 = new Mock<Die>(MockBehavior.Strict, 1, 2);
            mock1.SetupGet(die => die.MinimumValue).Returns(1);
            mock1.SetupGet(die => die.MaximumValue).Returns(2);
            mock1.Setup(die => die.Roll()).Returns(1);

            var mock2 = new Mock<Die>(MockBehavior.Strict, 1, 6);
            mock2.SetupGet(die => die.MinimumValue).Returns(1);
            mock2.SetupGet(die => die.MaximumValue).Returns(6);
            mock2.Setup(die => die.Roll()).Returns(6);

            var dice = new Dice(mock1.Object, mock2.Object);
            var result = dice.Roll();

            Assert.AreEqual(7, result);
        }

        [TestMethod()]
        public void RollShouldAllowOverflowException()
        {
            var mock1 = new Mock<Die>(MockBehavior.Strict, 1, Int32.MaxValue);
            mock1.Setup(die => die.Roll()).Returns(Int32.MaxValue);

            var mock2 = new Mock<Die>(MockBehavior.Strict, 1, Int32.MaxValue);
            mock2.Setup(die => die.Roll()).Returns(Int32.MaxValue);

            var dice = new Dice(mock1.Object, mock2.Object);

            try
            {
                dice.Roll();
                Assert.Fail("Exception should have been thrown");
            }
            catch (OverflowException exception)
            {
                Assert.AreEqual("Arithmetic operation resulted in an overflow.", exception.Message);
            }
        }
    }
}
